local famBags = LibStub('AceAddon-3.0'):GetAddon('famBags')

famBags.Classification = {}
famBags.Classification.BuiltCriterias = {}

_FAMBAGS_CLASSIFICATION = famBags.Classification

-- :::::::::::: LOCALS ::::::::::::: -------------------------------------------------------------------------

local wrong_categories = {}

local db
local CATEGORY_INDEXES = {}
local CURRENT_INFO
local TYPES = {}
local SUBTYPES = {}
local EQUIPSLOTS
local BOUNDSTATUS = {}


-- :::::::::::: famBags.Classification ::::::::::::: -------------------------------------------------------------------------

function famBags.Classification:GetButtonCategory(info, categories)
   db = famBags.db.profile.categoriesV2
   CURRENT_INFO = info
   local cat
   if info.id then
      for i, cat in pairs(categories) do
         local index = famBags.SortedCategories.byName[(type(cat) == 'number') and i or cat]
         cat = db[index]
         if cat and cat.Items and cat.Items[info.id] then
            return cat.category
         end
      end
   end
   local funccriteria, success, result
   for c, catindex in pairs(categories) do
      error = nil
      catindex = famBags.SortedCategories.byName[(type(catindex) == 'number') and c or catindex]
      funccriteria = self.BuiltCriterias[catindex] or self:BuildCriterias(catindex)
      cat = db[catindex]
      if cat and funccriteria and funccriteria ~= 'error' then
         success, result = pcall(assert(funccriteria))
         if success then
            if result then
               return cat.category
            end
         elseif result and not wrong_categories[cat.category] then
            self:SetWrongCategory(cat.category, result)
         end
      end
   end
   return nil
end

function famBags.Classification:ResetWrongCategories()
   wipe(wrong_categories)
end

function famBags.Classification:ResetBuiltCriterias()
   wipe(famBags.Classification.BuiltCriterias)
end

function famBags.Classification:BuildCriterias(index)
   local _db = db[index]
   if not _db then return end
   local t, func, error
   if _db.logic and #_db.logic > 0 and _db.criterias and #_db.criterias > 0 then
      t = _db.logic
      local range = famBags.Symbols[1]
      local c = #_db.criterias
      if c > 1 then
         range = '['..range..'-'..famBags.Symbols[c]..']'
      end
      if t == range then
         t = famBags.Classification:GetClassificationForSymbol(index, _db.criterias, nil, t, nil)
      else
         t = string.gsub(t, '^('..range..')([%p%s%c])', function(a, b) return famBags.Classification:GetClassificationForSymbol(index, _db.criterias, nil, a, b) end)
         t = string.gsub(t, '([%p%s%c])('..range..')$', function(a, b) return famBags.Classification:GetClassificationForSymbol(index, _db.criterias, a, b, nil) end)
         t = string.gsub(t, '([%p%s%c])('..range..')([%p%s%c])', function(a, b, c) return famBags.Classification:GetClassificationForSymbol(index, _db.criterias, a, b, c) end)
         t = string.gsub(t, '&&', ' and ')
         t = string.gsub(t, '||||', ' or ')
         t = string.gsub(t, '!', ' not ')
      end

      func, error = loadstring('return '..((t and t ~= '') and t or 'false'), 'error')

      if error and not wrong_categories[_db.category] then
         self:SetWrongCategory(_db.category, error)
         self.BuiltCriterias[index] = 'error'
      else
         self.BuiltCriterias[index] = func
      end
   end
   return self.BuiltCriterias[index]
end

function famBags.Classification:SetWrongCategory(category, error)
   print('|cffff3333famBags |cffffff00[|cff55FF55'..category..'|cffffff00]|cffff3333 error in logic:|r '..string.gsub(error, '.*:%d: ', ''))
   wrong_categories[category] = 1
end

function famBags.Classification:GetClassificationForSymbol(catindex, criterias, left, symbol, right)
   for i,c in ipairs(criterias) do
      if c.symbol == symbol then
         return (left or '')..'_FAMBAGS_CLASSIFICATION['..c.index..']('..catindex..', '..i..')'..(right or '')
      end
   end
end

function famBags.Classification:BuildTypes()
   for i,t in ipairs(famBags.Criterias[FAMBAGSCRITERIA_TYPESUBTYPE].options) do
      TYPES[t] = i
      SUBTYPES[i] = {}
      for j,s in ipairs(famBags.Subtypes[i]) do
         SUBTYPES[i][s] = j
      end
   end
end

function famBags.Classification:BuildEquipmentSlots()
   famBags.Criterias[FAMBAGSCRITERIA_EQUIPSLOT].strKeyedOptions = {}
   for i,t in ipairs(famBags.Criterias[FAMBAGSCRITERIA_EQUIPSLOT].options) do
      famBags.Criterias[FAMBAGSCRITERIA_EQUIPSLOT].strKeyedOptions[t] = i
   end
   EQUIPSLOTS = famBags.Criterias[FAMBAGSCRITERIA_EQUIPSLOT].strKeyedOptions
end

function famBags.Classification:BuildBoundStatus()
   for i,t in ipairs(famBags.Criterias[FAMBAGSCRITERIA_BOUNDSTATUS].options) do
      BOUNDSTATUS[t] = i
   end
end



-- :::::::::::: CRITERIAS ::::::::::::: -------------------------------------------------------------------------

famBags.Classification[FAMBAGSCRITERIA_TYPESUBTYPE] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if not (crit.type and CURRENT_INFO.type and CURRENT_INFO.subType) then return nil end
   return ((rawget(TYPES, CURRENT_INFO.type) == crit.type) and
          (rawget(crit.options, 0) or
           rawget(crit.options, rawget(SUBTYPES[crit.type], CURRENT_INFO.subType)))) and true
end

famBags.Classification[FAMBAGSCRITERIA_OTHERS] = function(catindex, critindex)
   return true
end

famBags.Classification[FAMBAGSCRITERIA_EMPTY] = function(catindex, critindex)
   return (not CURRENT_INFO.id)
end

famBags.Classification[FAMBAGSCRITERIA_QUEST] = function(catindex, critindex)
   return CURRENT_INFO.isQuestItem
end

famBags.Classification[FAMBAGSCRITERIA_USABLE] = function(catindex, critindex)
   return CURRENT_INFO.spell and true
end

famBags.Classification[FAMBAGSCRITERIA_KEY] = function(catindex, critindex)
   return (CURRENT_INFO.bag == KEYRING_CONTAINER)
end

famBags.Classification[FAMBAGSCRITERIA_NEW] = function(catindex, critindex)
   return CURRENT_INFO.new
end

famBags.Classification[FAMBAGSCRITERIA_INCREMENTED] = function(catindex, critindex)
   return CURRENT_INFO.added
end

famBags.Classification[FAMBAGSCRITERIA_QUALITY] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if rawget(crit.options, 0) then return true end
   local quality = (CURRENT_INFO.quality) and CURRENT_INFO.quality + 2 or 1
   return rawget(crit.options, quality) and true
end

famBags.Classification[FAMBAGSCRITERIA_EQUIPSET] = function(catindex, critindex)
   if not (CURRENT_INFO.id and famBags.EquipmentSets and next(famBags.EquipmentSets)) then return nil end
   local crit = db[catindex].criterias[critindex]
   for set, items in pairs(famBags.EquipmentSets) do
      if items and (rawget(crit.options, '0') or rawget(crit.options, set)) and rawget(items, CURRENT_INFO.id) then
         return true
      end
   end
   return nil
end

famBags.Classification[FAMBAGSCRITERIA_OUTFITTERSET] = function(catindex, critindex)
   if not (famBags.OUTFITTER and CURRENT_INFO.id and famBags.OutfitterSets and next(famBags.OutfitterSets)) then return false end
   local crit = db[catindex].criterias[critindex]
   for set, items in pairs(famBags.OutfitterSets) do
      if items and (rawget(crit.options, '0') or rawget(crit.options, set)) and rawget(items, CURRENT_INFO.id) then
         return true
      end
   end
   return nil
end

famBags.Classification[FAMBAGSCRITERIA_EQUIPSLOT] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   return (CURRENT_INFO.equipLoc and CURRENT_INFO.equipLoc ~= '' and
          (rawget(crit.options, 0) or rawget(crit.options, EQUIPSLOTS[_G[CURRENT_INFO.equipLoc]])))
end

famBags.Classification[FAMBAGSCRITERIA_BOUNDSTATUS] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   return (CURRENT_INFO.bound and CURRENT_INFO.bound ~= '' and
          (rawget(crit.options, 0) or rawget(crit.options, BOUNDSTATUS[CURRENT_INFO.bound])))
end

famBags.Classification[FAMBAGSCRITERIA_NAME] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   local operator = crit.comparer
   if not (CURRENT_INFO.name and crit.value and operator) then return false end
   if operator == 1 then -- Equal
      return CURRENT_INFO.name == crit.value
   elseif operator == 2 then -- Contains
      return strfind(CURRENT_INFO.name, crit.value) or false
   elseif operator == 3 then -- Starts with
      return strfind(CURRENT_INFO.name, '^'..crit.value) or false
   elseif operator == 4 then -- Ends with
      return strfind(CURRENT_INFO.name, crit.value..'$') or false
   end
   return false
end

famBags.Classification[FAMBAGSCRITERIA_SPELL] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   local operator = crit.comparer
   if not (CURRENT_INFO.spell and crit.value and operator) then return false end
   if operator == 1 then -- Equal
      return CURRENT_INFO.spell == crit.value
   elseif operator == 2 then -- Contains
      return strfind(CURRENT_INFO.spell, crit.value) or false
   elseif operator == 3 then -- Starts with
      return strfind(CURRENT_INFO.spell, '^'..crit.value) or false
   elseif operator == 4 then -- Ends with
      return strfind(CURRENT_INFO.spell, crit.value..'$') or false
   end
   return false
end

famBags.Classification[FAMBAGSCRITERIA_ITEMLEVEL] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if not crit.value then return false end
   local operator = crit.comparer
   if operator < 4 then
      local ilvl = tonumber(crit.value)
      if not (CURRENT_INFO.iLvl and operator and ilvl) then return false end
      if operator == 1 then -- Equal
         return CURRENT_INFO.iLvl == ilvl
      elseif operator == 2 then -- Greater or equal
         return CURRENT_INFO.iLvl >= ilvl
      elseif operator == 3 then -- Less or equal
         return CURRENT_INFO.iLvl <= ilvl
      end
   else
      local ilvl1, ilvl2 = strsplit(',', crit.value, 2)
      if not (ilvl1 and ilvl2) then return false end
      ilvl1 = tonumber(strtrim(ilvl1))
      ilvl2 = tonumber(strtrim(ilvl2))
      if not (ilvl1 and ilvl2) then return false end
      return CURRENT_INFO.iLvl >= ilvl1 and CURRENT_INFO.iLvl <= ilvl2
   end
   return false
end

famBags.Classification[FAMBAGSCRITERIA_REQUIREDLEVEL] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if not crit.value then return false end
   local operator = crit.comparer
   if operator < 4 then
      local lvl = tonumber(crit.value)
      if not (CURRENT_INFO.minLvl and operator and lvl) then return false end
      if operator == 1 then -- Equal
         return CURRENT_INFO.minLvl == lvl
      elseif operator == 2 then -- Greater or equal
         return CURRENT_INFO.minLvl >= lvl
      elseif operator == 3 then -- Less or equal
         return CURRENT_INFO.minLvl <= lvl
      end
   else
      local lvl1, lvl2 = strsplit(',', crit.value, 2)
      if not (lvl1 and lvl2) then return false end
      lvl1 = tonumber(strtrim(lvl1))
      lvl2 = tonumber(strtrim(lvl2))
      if not (lvl1 and lvl2) then return false end
      return CURRENT_INFO.minLvl >= lvl1 and CURRENT_INFO.minLvl <= lvl2
   end
   return false
end

famBags.Classification[FAMBAGSCRITERIA_STATS] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if not (CURRENT_INFO.stats and next(CURRENT_INFO.stats)) then return false end
   if rawget(crit.options, '0') then return true end
   for _,s in ipairs(CURRENT_INFO.stats) do
      if rawget(crit.options, s) then return true end
   end
   return false
end

famBags.Classification[FAMBAGSCRITERIA_CATEGORIES] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if not next(crit.options) then return false end
   local cat = famBags.Classification:GetButtonCategory(CURRENT_INFO, crit.options)
   return (cat ~= nil)
end

famBags.Classification[FAMBAGSCRITERIA_CONTAINERTYPE] = function(catindex, critindex)
   local crit = db[catindex].criterias[critindex]
   if ((not CURRENT_INFO.bagType or CURRENT_INFO.bagType == 0) and
       (not next(crit.options) or rawget(crit.options, 0))) then
      return true
   end
   if rawget(crit.options, CURRENT_INFO.bagType) then return true end
   return false
end


-- :::::::::::: INIT ::::::::::::: -------------------------------------------------------------------------

famBags.Classification:BuildTypes()
famBags.Classification:BuildEquipmentSlots()
famBags.Classification:BuildBoundStatus()